package com.jzo2o.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.enums.CouponConfigStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.CouponUserMapper;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponUser;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.CouponRedeemReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponUserInfoResDTO;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponUserService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.common.constants.ErrorInfo.Code.SEIZE_COUPON_FAILD;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class CouponUserServiceImpl extends ServiceImpl<CouponUserMapper, CouponUser> implements ICouponUserService {

    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<String> seizeCouponScript;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponUseBackService couponUseBackService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Override
    public PageResult<CouponUserInfoResDTO> queryForPageOfOperation(CouponOperationPageQueryReqDTO couponOperationPageQueryReqDTO) {
        // 1.数据校验
        if (ObjectUtils.isNull(couponOperationPageQueryReqDTO.getCouponId())) {
            throw new BadRequestException("请指定活动");
        }
        // 2.数据查询
        // 分页 排序
        Page<CouponUser> couponQueryPage = PageUtils.parsePageQuery(couponOperationPageQueryReqDTO, CouponUser.class);
        // 查询条件
        LambdaQueryWrapper<CouponUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CouponUser::getCouponId, couponOperationPageQueryReqDTO.getCouponId());
        // 查询数据
        Page<CouponUser> couponPage = baseMapper.selectPage(couponQueryPage, lambdaQueryWrapper);

        // 3.数据转化，并返回
        return PageUtils.toPage(couponPage, CouponUserInfoResDTO.class);
    }

    @Override
    public List<CouponUserInfoResDTO> queryForList(Long lastId, Long userId, Integer status) {

        // 1.校验
        if (status > 3 || status < 1) {
            throw new BadRequestException("请求状态不存在");
        }
        // 2.查询准备
        LambdaQueryWrapper<CouponUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper.eq(CouponUser::getStatus, status)
                .eq(CouponUser::getUserId, userId)
                .lt(ObjectUtils.isNotNull(lastId), CouponUser::getId, lastId);
        // 查询字段
        lambdaQueryWrapper.select(CouponUser::getId);
        // 排序
        lambdaQueryWrapper.orderByDesc(CouponUser::getId);
        // 查询条数限制
        lambdaQueryWrapper.last(" limit 10 ");
        // 3.查询数据(数据中只含id)
        List<CouponUser> couponsOnlyId = baseMapper.selectList(lambdaQueryWrapper);
        //判空
        if (CollUtils.isEmpty(couponsOnlyId)) {
            return new ArrayList<>();
        }

        // 4.获取数据且数据转换
        // 优惠id列表
        List<Long> ids = couponsOnlyId.stream()
                .map(CouponUser::getId)
                .collect(Collectors.toList());
        // 获取优惠券数据
        List<CouponUser> couponUserList = baseMapper.selectBatchIds(ids);

        //将列表按照id倒序排列
        CollUtil.sort(couponUserList, Comparator.comparing(CouponUser::getId).reversed());

        // 数据转换
        return BeanUtils.copyToList(couponUserList, CouponUserInfoResDTO.class);
    }

    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        // 1.校验活动开始时间或结束
        // 抢券时间
        CouponInfoResDTO couponInfoResDTO = couponService.getCouponInfoByIdFromCache(seizeCouponReqDTO.getId());
        LocalDateTime now = DateUtils.now();
        if (couponInfoResDTO == null ||
                couponInfoResDTO.getDistributeStartTime().isAfter(now)) {
            throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
        }
        if (couponInfoResDTO.getDistributeEndTime().isBefore(now)) {
            throw new CommonException(SEIZE_COUPON_FAILD, "活动已结束");
        }

        // 2.抢券准备
//         key: 抢券同步队列，资源库存,抢券列表
//         argv：抢券id,用户id
        int index = (int) (seizeCouponReqDTO.getId() % 10);
        // 同步队列redisKey
        String couponSeizeSyncRedisKey = RedisSyncQueueUtils.getQueueRedisKey(COUPON_SEIZE_SYNC_QUEUE_NAME, index);
        // 资源库存redisKey
        String resourceStockRedisKey = String.format(COUPON_RESOURCE_STOCK, index);
        // 抢券列表
        String couponSeizeListRedisKey = String.format(COUPON_SEIZE_LIST, couponInfoResDTO.getId(), index);

        log.debug("seize coupon keys -> couponSeizeListRedisKey->{},resourceStockRedisKey->{},couponSeizeListRedisKey->{},seizeCouponReqDTO.getId()->{},UserContext.currentUserId():{}",
                couponSeizeListRedisKey, resourceStockRedisKey, couponSeizeListRedisKey, seizeCouponReqDTO.getId(), UserContext.currentUserId());
        // 3.抢券结果
        Object execute = redisTemplate.execute(seizeCouponScript, Arrays.asList(couponSeizeSyncRedisKey, resourceStockRedisKey, couponSeizeListRedisKey),
                seizeCouponReqDTO.getId(), UserContext.currentUserId());
        log.debug("seize coupon result : {}", execute);
        // 4.处理lua脚本结果
        if (execute == null) {
            throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
        }
        long result = NumberUtils.parseLong(execute.toString());
        if (result > 0) {
            return;
        }
        if (result == -1) {
            throw new CommonException(SEIZE_COUPON_FAILD, "限领一张");
        }
        if (result == -2) {
            throw new CommonException(SEIZE_COUPON_FAILD, "已抢光!");
        }
        throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
    }

    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount, Long userId) {
        // 1.查询优惠券
        List<CouponUser> coupons = lambdaQuery()
                .eq(CouponUser::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .gt(CouponUser::getValidityTime, DateUtils.now())
                .le(CouponUser::getAmountCondition, totalAmount)
                .eq(CouponUser::getUserId, userId)
                .list();
        // 判空
        if (CollUtils.isEmpty(coupons)) {
            return new ArrayList<>();
        }

        //筛选出来满足优惠门槛的优惠券
        List<AvailableCouponsResDTO> availableCouponsList = coupons.stream()
                .filter(coupon -> coupon.getAmountCondition().compareTo(totalAmount) <= 0)
                .map(coupon -> BeanUtils.copyBean(coupon, AvailableCouponsResDTO.class))
                .collect(Collectors.toList());

        // 计算实际优惠金额
        availableCouponsList.forEach(a -> {
            BigDecimal realDiscountAmount = CouponUtils.calDiscountAmount(a.getType(), a.getAmountCondition(), a.getDiscountAmount(), a.getDiscountRate(), totalAmount);
            a.setRealDiscountAmount(realDiscountAmount);
        });

        //按照实际优惠金额倒序排列
        CollUtil.sort(availableCouponsList, Comparator.comparing(AvailableCouponsResDTO::getRealDiscountAmount).reversed());
        return availableCouponsList;
    }

    @Override
    @Transactional
    public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
        // 1.校验优惠券
        // 1.1.获取优惠券
        CouponUser couponUser = baseMapper.selectById(couponUseReqDTO.getId());
        // 1.2.优惠券判空
        if (couponUser == null) {
            throw new BadRequestException("优惠券不存在");
        }

        // 2.使用优惠券
        boolean update = lambdaUpdate()
                .set(CouponUser::getStatus, CouponStatusEnum.USED.getStatus())
                .set(CouponUser::getUseTime, DateUtils.now())
                .set(CouponUser::getOrdersId, couponUseReqDTO.getOrdersId())
                .eq(CouponUser::getId, couponUseReqDTO.getId())
                .eq(CouponUser::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .ge(CouponUser::getValidityTime, DateUtils.now())
                .le(CouponUser::getAmountCondition, couponUseReqDTO.getTotalAmount())
                .update();
        if (!update) {
            throw new DBException("优惠券已失效");
        }
        // 3.添加核销记录
        CouponWriteOff couponWriteOff = CouponWriteOff.builder()
                .id(IdUtils.getSnowflakeNextId())
                .couponUserId(couponUseReqDTO.getId())
                .userId(couponUseReqDTO.getUserId())
                .ordersId(couponUseReqDTO.getOrdersId())
                .couponId(couponUser.getCouponId())
                .writeOffTime(DateUtils.now())
                .writeOffManName(couponUser.getUserName())
                .writeOffManPhone(couponUser.getUserPhone())
                .build();
        if (!couponWriteOffService.save(couponWriteOff)) {
            throw new DBException("操作失败");
        }

        // 4.计算优惠金额
        BigDecimal discountAmount = CouponUtils.calDiscountAmount(couponUser, couponUseReqDTO.getTotalAmount());
        CouponUseResDTO couponUseResDTO = new CouponUseResDTO();
        couponUseResDTO.setDiscountAmount(discountAmount);
        return couponUseResDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {
        // 1.校验是否可以回退
        CouponWriteOff couponWriteOff = couponWriteOffService.queryByUserIdIdAndOrdersId(couponUseBackReqDTO.getUserId(), couponUseBackReqDTO.getOrdersId());
        // 未查询到无需回滚
        if (couponWriteOff == null) {
            return;
        }
        CouponUser couponUser = baseMapper.selectById(couponWriteOff.getCouponUserId());
        if (couponUser == null) {
            return;
        }

        Coupon coupon = couponService.getById(couponWriteOff.getCouponId());
        // 2.回退记录
        couponUseBackService.add(couponWriteOff.getCouponUserId(), couponUseBackReqDTO.getUserId(), couponWriteOff.getWriteOffTime());

        // 3.回滚优惠券
        CouponStatusEnum couponStatusEnum = couponUser.getValidityTime().isAfter(DateUtils.now())
                ? CouponStatusEnum.NO_USE : CouponStatusEnum.INVALID;
        if (CouponConfigStatusEnum.LOSE_EFFICACY.equals(coupon.getStatus())) {
            // 活动作废
            couponStatusEnum = CouponStatusEnum.INVALID;

        }
        boolean update = lambdaUpdate()
                .set(CouponUser::getStatus, couponStatusEnum.getStatus())
                .set(CouponUser::getOrdersId, null)
                .set(CouponUser::getUseTime, null)
                .eq(CouponUser::getId, couponUser.getId())
                .update();
        if (!update) {
            throw new RuntimeException("优惠券回退失败");
        }
        // 4.删除核销记录
        couponWriteOffService.removeById(couponWriteOff.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long couponId) {
        lambdaUpdate()
                .set(CouponUser::getStatus, CouponStatusEnum.INVALID.getStatus())
                .eq(CouponUser::getCouponId, couponId)
                .eq(CouponUser::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .update();
    }

    @Override
    public Integer countReceiveNumByCouponId(Long couponId) {
        return lambdaQuery().eq(CouponUser::getCouponId, couponId)
                .count().intValue();
    }

    @Override
    public void processExpireCoupon() {
        lambdaUpdate()
                .set(CouponUser::getStatus, CouponStatusEnum.INVALID.getStatus())
                .eq(CouponUser::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .le(CouponUser::getValidityTime, DateUtils.now())
                .update();
    }

    /**
     * 兑换优惠券
     *
     * @param couponRedeemReqDTO 优惠券兑换请求模型
     */
    @Override
    @Transactional
    public void redeemCoupon(CouponRedeemReqDTO couponRedeemReqDTO) {
        // 1.校验活动开始时间或结束
        Coupon coupon = couponService.findByRedeemCode(couponRedeemReqDTO.getRedeemCode());
        if (coupon == null || coupon.getDistributeStartTime().isAfter(LocalDateTime.now())) {
            throw new CommonException("优惠券兑换码错误");
        }

        if (coupon.getDistributeEndTime().isBefore(LocalDateTime.now())) {
            throw new CommonException("优惠券兑换码错误");
        }

        //2.校验库存是否充足
        if (coupon.getDrawNum() >= coupon.getTotalNum()) {
            throw new CommonException("您来晚了，优惠券已飞入他人之手");
        }

        //3.校验是否重复兑换
        CouponUser couponUser = lambdaQuery().eq(CouponUser::getCouponId, coupon.getId())
                .eq(CouponUser::getUserId, couponRedeemReqDTO.getUserId())
                .one();
        if (ObjectUtil.isNotEmpty(couponUser)) {
            throw new CommonException("优惠券不能重复兑换");
        }

        //4.扣减库存
        boolean isSuccess = couponService.deductionStock(coupon.getId(), coupon.getDrawNum());
        if (!isSuccess) {
            throw new CommonException("优惠券兑换失败");
        }

        //5.新增优惠券与用户关联记录
        CouponUser newCouponUser = BeanUtil.toBean(coupon, CouponUser.class);
        newCouponUser.setId(null);
        newCouponUser.setName(coupon.getName());
        newCouponUser.setUserId(couponRedeemReqDTO.getUserId());
        newCouponUser.setUserName(couponRedeemReqDTO.getUserName());
        newCouponUser.setUserPhone(couponRedeemReqDTO.getUserPhone());
        newCouponUser.setCouponId(coupon.getId());
        newCouponUser.setValidityTime(LocalDateTime.now().plusDays(coupon.getValidityDays()));
        newCouponUser.setStatus(CouponStatusEnum.NO_USE.getStatus());
        baseMapper.insert(newCouponUser);
    }
}
